package com.jerry.security.authentication;

import com.jerry.security.config.SecurityConfigProperties;
import com.jerry.security.validate.ValidateCode;
import com.jerry.security.validate.ValidateCodeType;
import com.jerry.security.validate.image.ImageCode;
import com.jerry.security.validate.ValidateCodeException;
import com.jerry.security.validate.sms.SmsCode;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.util.AntPathMatcher;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class ValidateCodeAuthFilter extends OncePerRequestFilter implements InitializingBean {

    /**
     * 存放所有需要校验验证码的url
     */
    private Map<String, ValidateCodeType> urlMap = new HashMap<String, ValidateCodeType>();
    /**
     * 验证码配置信息
     */
    private SecurityConfigProperties securityConfigProperties;
    /**
     * 验证请求url与配置的url是否匹配的工具类
     */
    private AntPathMatcher pathMatcher = new AntPathMatcher();
    /**
     * 验证码校验失败处理器
     */
    private AuthenticationFailureHandler authenticationFailureHandler;

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    public SecurityConfigProperties getSecurityConfigProperties() {
        return securityConfigProperties;
    }
    public void setSecurityConfigProperties(SecurityConfigProperties securityConfigProperties) {
        this.securityConfigProperties = securityConfigProperties;
    }

    /**
     * 讲系统中配置的需要校验验证码的URL根据校验的类型放入map
     *
     * @param urlString
     * @param type
     */
    protected void addUrlToMap(String urlString, ValidateCodeType type) {
        if (StringUtils.isNotBlank(urlString)) {
            String[] urls = StringUtils.split(urlString, ",");
            for (String url : urls) {
                urlMap.put(url, type);
            }
        }
    }

    /**
     * 初始化要拦截的url配置信息
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        urlMap.put("/authentication/formLogin", ValidateCodeType.IMAGE);
        addUrlToMap(securityConfigProperties.getImageCode().getUrl(), ValidateCodeType.IMAGE);

        urlMap.put("/authentication/mobileLogin", ValidateCodeType.SMS);
        addUrlToMap(securityConfigProperties.getSmsCode().getUrl(), ValidateCodeType.SMS);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        ValidateCodeType type = getValidateCodeType(request);
        if (type != null) {
            System.out.println("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);
            try {
                validate(request, type);
            } catch (ValidateCodeException exception) {
                //交给自定义AuthentFailureHandler处理
                authenticationFailureHandler.onAuthenticationFailure(request,response,exception);
                return;
            }
        }
        filterChain.doFilter(request,response);
    }

    /**
     * 获取校验码的类型，如果当前请求不需要校验，则返回null
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
        ValidateCodeType result = null;
        if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {
            Set<String> urls = urlMap.keySet();
            for (String url : urls) {
                if (pathMatcher.match(url, request.getRequestURI())) {
                    result = urlMap.get(url);
                }
            }
        }
        return result;
    }

    private void validate(HttpServletRequest request, ValidateCodeType type) {
        //获取用户输入的验证码
        String codeInRequest = request.getParameter(type.getParamNameOnValidate());
        //获取系统生成的验证码
        ValidateCode codeInSession = null;
        if (StringUtils.equalsIgnoreCase(type.getParamNameOnValidate(), ValidateCodeType.IMAGE.getParamNameOnValidate())) {
            codeInSession = (ImageCode)request.getSession().getAttribute(type.getParamNameOnValidate());
        } else if(StringUtils.equalsIgnoreCase(type.getParamNameOnValidate(), ValidateCodeType.SMS.getParamNameOnValidate())) {
            codeInSession = (SmsCode)request.getSession().getAttribute(type.getParamNameOnValidate());
        }
        //若request中不存在验证码，则提示"验证码未传入"
        if(codeInRequest == null){
            throw new ValidateCodeException( "验证码必须输入");
        }
        //若request中的验证码为空，则提示"验证码必须输入"
        if(StringUtils.isEmpty(codeInRequest.trim())){
            throw new ValidateCodeException( "验证码必须输入");
        }
        //若session中不存在验证码，则提示"验证码不存在"
        if (codeInSession == null) {
            throw new ValidateCodeException( "验证码不存在");
        }
        //若session中的验证码过期，则提示"验证码过期"
        if (codeInSession.isExpried()) {
            throw new ValidateCodeException( "验证码过期");
        }
        //若用户输入的验证码和系统生成的不匹配，则提示"验证码不一致"
        if(!codeInRequest.trim().equals(codeInSession.getCode())){
            throw new ValidateCodeException( "验证码不一致");
        }
        //从session中移除验证码
        request.getSession().removeAttribute(type.getParamNameOnValidate());
    }

}
